// here, the extra check for window['Ext'] is needed for use with cmd-test
// code injection. we need to make that this file will sync up with page global
// scope to avoid duplicate Ext.Boot state. That check is after the initial Ext
// check
// to allow the sandboxing template to inject an appropriate Ext var and prevent
// the
// global detection.
var Ext = Ext || window['Ext'] || {};

// <editor-fold desc="Microloader">
/**
 * @class Ext.Microloader
 * @private
 * @singleton
 */
Ext.Microloader = Ext.Microloader || (function() {
	var Boot = Ext.Boot,
	// <debug>
	_debug = function(message) {
		// console.log(message);
	},
	// </debug>
	_warn = function(message) {
		console.log("[WARN] " + message);
	}, _privatePrefix = '_ext:' + location.pathname,

	/**
	 * @method getStorageKey The Following combination is used to create
	 *         isolated local storage keys '_ext' is used to scope all the local
	 *         storage keys that we internally by Ext 'location.pathname' is
	 *         used to force each assets to cache by an absolute URL
	 *         (/build/MyApp) (dev vs prod) 'url' is used to force each asset to
	 *         cache relative to the page (app.json vs resources/app.css)
	 *         'profileId' is used to differentiate the builds of an application
	 *         (neptune vs crisp) 'Microloader.appId' is unique to the
	 *         application and will differentiate apps on the same host (dev
	 *         mode running app watch against multiple apps)
	 */
	getStorageKey = function(url, profileId) {
		return _privatePrefix + url + '-' + (profileId ? profileId + '-' : '')
				+ Microloader.appId;
	}, postProcessor, _storage;

	try {
		_storage = window['localStorage'];
	} catch (ex) {
		// ignore
	}

	var _cache = window['applicationCache'],
	// Local Storage Controller
	LocalStorage = {
		clearAllPrivate : function(manifest) {
			if (_storage) {

				// Remove the entry for the manifest first
				_storage.removeItem(manifest.key);

				var i, key, removeKeys = [], suffix = manifest.profile + '-'
						+ Microloader.appId, ln = _storage.length;
				for (i = 0; i < ln; i++) {
					key = _storage.key(i);
					// If key starts with the private key and the suffix is
					// present we can clear this entry
					if (key.indexOf(_privatePrefix) === 0
							&& key.indexOf(suffix) !== -1) {
						removeKeys.push(key);
					}
				}

				for (i in removeKeys) {
					// <debug>
					_debug("Removing " + removeKeys[i] + " from Local Storage");
					// </debug>
					_storage.removeItem(removeKeys[i]);
				}
			}
		},
		/**
		 * @private
		 */
		retrieveAsset : function(key) {
			try {
				return _storage.getItem(key);
			} catch (e) {
				// Private browsing mode
				return null;
			}
		},

		setAsset : function(key, content) {
			try {
				if (content === null || content == '') {
					_storage.removeItem(key);
				} else {
					_storage.setItem(key, content);
				}
			} catch (e) {
				if (_storage && e.code == e.QUOTA_EXCEEDED_ERR) {
					// <debug>
					_warn("LocalStorage Quota exceeded, cannot store " + key
							+ " locally");
					// </debug>
				}
			}
		}
	};

	var Asset = function(cfg) {
		if (typeof cfg.assetConfig === 'string') {
			this.assetConfig = {
				path : cfg.assetConfig
			};
		} else {
			this.assetConfig = cfg.assetConfig;
		}

		this.type = cfg.type;
		this.key = getStorageKey(this.assetConfig.path, cfg.manifest.profile);

		if (cfg.loadFromCache) {
			this.loadFromCache();
		}
	};

	Asset.prototype = {
		shouldCache : function() {
			return _storage && this.assetConfig.update && this.assetConfig.hash
					&& !this.assetConfig.remote;
		},

		is : function(asset) {
			return (!!asset && this.assetConfig && asset.assetConfig && (this.assetConfig.hash === asset.assetConfig.hash))
		},

		cache : function(content) {
			if (this.shouldCache()) {
				LocalStorage.setAsset(this.key, content || this.content);
			}
		},

		uncache : function() {
			LocalStorage.setAsset(this.key, null);
		},

		updateContent : function(content) {
			this.content = content;
		},

		getSize : function() {
			return this.content ? this.content.length : 0;
		},

		loadFromCache : function() {
			if (this.shouldCache()) {
				this.content = LocalStorage.retrieveAsset(this.key);
			}
		}
	};

	var Manifest = function(cfg) {
		if (typeof cfg.content === "string") {
			this.content = JSON.parse(cfg.content);
		} else {
			this.content = cfg.content;
		}
		this.assetMap = {};

		this.url = cfg.url;
		this.fromCache = !!cfg.cached;
		this.assetCache = !(cfg.assetCache === false);
		this.key = getStorageKey(this.url);

		// Pull out select properties for repetitive use
		this.profile = this.content.profile;
		this.hash = this.content.hash;
		this.loadOrder = this.content.loadOrder;
		this.deltas = this.content.cache ? this.content.cache.deltas : null;
		this.cacheEnabled = this.content.cache
				? this.content.cache.enable
				: false;

		this.loadOrderMap = (this.loadOrder) ? Boot
				.createLoadOrderMap(this.loadOrder) : null;

		var tags = this.content.tags, platformTags = Ext.platformTags;

		if (tags) {
			if (tags instanceof Array) {
				for (var i = 0; i < tags.length; i++) {
					platformTags[tags[i]] = true;
				}
			} else {
				Boot.apply(platformTags, tags);
			}

			// re-apply the query parameters, so that the params as specified
			// in the url always has highest priority
			Boot.apply(platformTags, Boot.loadPlatformsParam());
		}

		// Convert all assets into Assets
		this.js = this.processAssets(this.content.js, 'js');
		this.css = this.processAssets(this.content.css, 'css');
	};

	Manifest.prototype = {
		processAsset : function(assetConfig, type) {
			var processedAsset = new Asset({
						manifest : this,
						assetConfig : assetConfig,
						type : type,
						loadFromCache : this.assetCache
					});
			this.assetMap[assetConfig.path] = processedAsset;
			return processedAsset;
		},

		processAssets : function(assets, type) {
			var results = [], ln = assets.length, i, assetConfig;

			for (i = 0; i < ln; i++) {
				assetConfig = assets[i];
				results.push(this.processAsset(assetConfig, type));
			}

			return results;
		},

		useAppCache : function() {
			return true;
		},

		// Concatenate all assets for easy access
		getAssets : function() {
			return this.css.concat(this.js);
		},

		getAsset : function(path) {
			return this.assetMap[path];
		},

		shouldCache : function() {
			return this.hash && this.cacheEnabled;
		},

		cache : function(content) {
			if (this.shouldCache()) {
				LocalStorage.setAsset(this.key, JSON.stringify(content
								|| this.content));
			}
			// <debug>
			else {
				_debug("Manifest caching is disabled.");
			}
			// </debug>
		},

		is : function(manifest) {
			// <debug>
			_debug("Testing Manifest: " + this.hash + " VS " + manifest.hash);
			// </debug>
			return this.hash === manifest.hash;
		},

		// Clear the manifest from local storage
		uncache : function() {
			LocalStorage.setAsset(this.key, null);
		},

		exportContent : function() {
			return Boot.apply({
						loadOrderMap : this.loadOrderMap
					}, this.content);
		}
	};

	/**
	 * Microloader
	 * 
	 * @type {Array}
	 * @private
	 */
	var _listeners = [], _loaded = false, Microloader = {
		init : function() {
			Ext.microloaded = true;

			// data-app is in the dev template for an application and is also
			// injected into the app my CMD for production
			// We use this to prefix localStorage cache to prevent collisions
			var microloaderElement = document.getElementById('microloader');
			Microloader.appId = microloaderElement ? microloaderElement
					.getAttribute('data-app') : '';

			if (Ext.beforeLoad) {
				postProcessor = Ext.beforeLoad(Ext.platformTags);
			}

			var readyHandler = Ext._beforereadyhandler;

			Ext._beforereadyhandler = function() {
				if (Ext.Boot !== Boot) {
					Ext.apply(Ext.Boot, Boot);
					Ext.Boot = Boot;
				}
				if (readyHandler) {
					readyHandler();
				}
			};
		},

		applyCacheBuster : function(url) {
			var tstamp = new Date().getTime(), sep = url.indexOf('?') === -1
					? '?'
					: '&';
			url = url + sep + "_dc=" + tstamp;
			return url;
		},

		run : function() {
			Microloader.init();
			var manifest = Ext.manifest;

			if (typeof manifest === "string") {
				var extension = ".json", url = manifest.indexOf(extension) === manifest.length
						- extension.length ? manifest : manifest + ".json", key = getStorageKey(url), content = LocalStorage
						.retrieveAsset(key);

				// Manifest found in local storage, use this for immediate boot
				// except in PhantomJS environments for building.
				if (content) {
					// <debug>
					_debug("Manifest file, '" + url
							+ "', was found in Local Storage");
					// </debug>
					manifest = new Manifest({
								url : url,
								content : content,
								cached : true
							});
					if (postProcessor) {
						postProcessor(manifest);
					}
					Microloader.load(manifest);

					// Manifest is not in local storage. Fetch it from the
					// server
				} else {
					// <debug>
					_debug("Manifest file was not found in Local Storage, loading: "
							+ url);
					// </debug>

					if (location.href.indexOf('file:/') === 0) {
						Manifest.url = Microloader.applyCacheBuster(url + 'p');
						Boot.load(Manifest.url);
					} else {
						Manifest.url = url;
						Boot.fetch(Microloader.applyCacheBuster(url), function(
										result) {
									Microloader.setManifest(result.content);
								});
					}
				}

				// Embedded Manifest into JS file
			} else {
				// <debug>
				_debug("Manifest was embedded into application javascript file");
				// </debug>
				manifest = new Manifest({
							content : manifest
						});
				Microloader.load(manifest);
			}
		},

		/**
		 * 
		 * @param cfg
		 */
		setManifest : function(cfg) {
			var manifest = new Manifest({
						url : Manifest.url,
						content : cfg
					});
			manifest.cache();
			if (postProcessor) {
				postProcessor(manifest);
			}
			Microloader.load(manifest);
		},

		/**
		 * @param {Manifest}
		 *            manifest
		 */
		load : function(manifest) {
			Microloader.urls = [];
			Microloader.manifest = manifest;
			Ext.manifest = Microloader.manifest.exportContent();

			var assets = manifest.getAssets(), cachedAssets = [], asset, i, len, include, entry;

			for (len = assets.length, i = 0; i < len; i++) {
				asset = assets[i];
				include = Microloader.filterAsset(asset);
				if (include) {
					// Asset is using the localStorage caching system
					if (manifest.shouldCache() && asset.shouldCache()) {
						// Asset already has content from localStorage,
						// instantly seed that into boot
						if (asset.content) {
							// <debug>
							_debug("Asset: "
									+ asset.assetConfig.path
									+ " was found in local storage. No remote load for this file");
							// </debug>
							entry = Boot.registerContent(
									asset.assetConfig.path, asset.type,
									asset.content);
							if (entry.evaluated) {
								_warn("Asset: "
										+ asset.assetConfig.path
										+ " was evaluated prior to local storage being consulted.");
							}
							// load via AJAX and seed content into Boot
						} else {
							// <debug>
							_debug("Asset: "
									+ asset.assetConfig.path
									+ " was NOT found in local storage. Adding to load queue");
							// </debug>
							cachedAssets.push(asset);
						}
					}
					Microloader.urls.push(asset.assetConfig.path);
					Boot.assetConfig[asset.assetConfig.path] = Boot.apply({
								type : asset.type
							}, asset.assetConfig);
				}
			}

			// If any assets are using the caching system and do not have local
			// versions load them first via AJAX
			if (cachedAssets.length > 0) {
				Microloader.remainingCachedAssets = cachedAssets.length;
				while (cachedAssets.length > 0) {
					asset = cachedAssets.pop();
					// <debug>
					_debug("Preloading/Fetching Cached Assets from: "
							+ asset.assetConfig.path);
					// </debug>
					Boot.fetch(asset.assetConfig.path, (function(asset) {
								return function(result) {
									Microloader.onCachedAssetLoaded(asset,
											result);
								}
							})(asset));
				}
			} else {
				Microloader.onCachedAssetsReady();
			}
		},

		// Load the asset and seed its content into Boot to be evaluated in
		// sequence
		onCachedAssetLoaded : function(asset, result) {
			var checksum;
			result = Microloader.parseResult(result);
			Microloader.remainingCachedAssets--;

			if (!result.error) {
				checksum = Microloader.checksum(result.content,
						asset.assetConfig.hash);
				if (!checksum) {
					_warn("Cached Asset '"
							+ asset.assetConfig.path
							+ "' has failed checksum. This asset will be uncached for future loading");

					// Un cache this asset so it is loaded next time
					asset.uncache();
				}

				// <debug>
				_debug("Checksum for Cached Asset: " + asset.assetConfig.path
						+ " is " + checksum);
				// </debug>
				Boot.registerContent(asset.assetConfig.path, asset.type,
						result.content);
				asset.updateContent(result.content);
				asset.cache();
			} else {
				_warn("There was an error pre-loading the asset '"
						+ asset.assetConfig.path
						+ "'. This asset will be uncached for future loading");

				// Un cache this asset so it is loaded next time
				asset.uncache();
			}

			if (Microloader.remainingCachedAssets === 0) {
				Microloader.onCachedAssetsReady();
			}
		},

		onCachedAssetsReady : function() {
			Boot.load({
						url : Microloader.urls,
						loadOrder : Microloader.manifest.loadOrder,
						loadOrderMap : Microloader.manifest.loadOrderMap,
						sequential : true,
						success : Microloader.onAllAssetsReady,
						failure : Microloader.onAllAssetsReady
					});
		},

		onAllAssetsReady : function() {
			_loaded = true;
			Microloader.notify();

			if (navigator.onLine !== false) {
				// <debug>
				_debug("Application is online, checking for updates");
				// </debug>
				Microloader.checkAllUpdates();
			} else {
				// <debug>
				_debug("Application is offline, adding online listener to check for updates");
				// </debug>
				if (window['addEventListener']) {
					window.addEventListener('online',
							Microloader.checkAllUpdates, false);
				}
			}
		},

		onMicroloaderReady : function(listener) {
			if (_loaded) {
				listener();
			} else {
				_listeners.push(listener);
			}
		},

		/**
		 * @private
		 */
		notify : function() {
			// <debug>
			_debug("notifying microloader ready listeners.");
			// </debug>
			var listener;
			while ((listener = _listeners.shift())) {
				listener();
			}
		},

		// Delta patches content
		patch : function(content, delta) {
			var output = [], chunk, i, ln;

			if (delta.length === 0) {
				return content;
			}

			for (i = 0, ln = delta.length; i < ln; i++) {
				chunk = delta[i];

				if (typeof chunk === 'number') {
					output.push(content.substring(chunk, chunk + delta[++i]));
				} else {
					output.push(chunk);
				}
			}

			return output.join('');
		},

		checkAllUpdates : function() {
			// <debug>
			_debug("Checking for All Updates");
			// </debug>
			if (window['removeEventListener']) {
				window.removeEventListener('online',
						Microloader.checkAllUpdates, false);
			}

			if (_cache) {
				Microloader.checkForAppCacheUpdate();
			}

			// Manifest came from a cached instance, check for updates
			if (Microloader.manifest.fromCache) {
				Microloader.checkForUpdates();
			}
		},

		checkForAppCacheUpdate : function() {
			// <debug>
			_debug("Checking App Cache status");
			// </debug>
			if (_cache.status === _cache.UPDATEREADY
					|| _cache.status === _cache.OBSOLETE) {
				// <debug>
				_debug("App Cache is already in an updated");
				// </debug>
				Microloader.appCacheState = 'updated';
			} else if (_cache.status !== _cache.IDLE
					&& _cache.status !== _cache.UNCACHED) {
				// <debug>
				_debug("App Cache is checking or downloading updates, adding listeners");
				// </debug>
				Microloader.appCacheState = 'checking';
				_cache.addEventListener('error', Microloader.onAppCacheError);
				_cache.addEventListener('noupdate',
						Microloader.onAppCacheNotUpdated);
				_cache.addEventListener('cached',
						Microloader.onAppCacheNotUpdated);
				_cache.addEventListener('updateready',
						Microloader.onAppCacheReady);
				_cache.addEventListener('obsolete',
						Microloader.onAppCacheObsolete);
			} else {
				// <debug>
				_debug("App Cache is current or uncached");
				// </debug>
				Microloader.appCacheState = 'current';
			}
		},

		checkForUpdates : function() {
			// Fetch the Latest Manifest from the server
			// <debug>
			_debug("Checking for updates at: " + Microloader.manifest.url);
			// </debug>
			Boot.fetch(Microloader.applyCacheBuster(Microloader.manifest.url),
					Microloader.onUpdatedManifestLoaded);
		},

		onAppCacheError : function(e) {
			_warn(e.message);

			Microloader.appCacheState = 'error';
			Microloader.notifyUpdateReady();
		},

		onAppCacheReady : function() {
			_cache.swapCache();
			Microloader.appCacheUpdated();
		},

		onAppCacheObsolete : function() {
			Microloader.appCacheUpdated();
		},

		appCacheUpdated : function() {
			// <debug>
			_debug("App Cache Updated");
			// </debug>
			Microloader.appCacheState = 'updated';
			Microloader.notifyUpdateReady();
		},

		onAppCacheNotUpdated : function() {
			// <debug>
			_debug("App Cache Not Updated Callback");
			// </debug>
			Microloader.appCacheState = 'current';
			Microloader.notifyUpdateReady();
		},

		filterAsset : function(asset) {
			var cfg = (asset && asset.assetConfig) || {};
			if (cfg.platform || cfg.exclude) {
				return Boot.filterPlatform(cfg.platform, cfg.exclude);
			}
			return true;
		},

		onUpdatedManifestLoaded : function(result) {
			result = Microloader.parseResult(result);

			if (!result.error) {
				var currentAssets, newAssets, currentAsset, newAsset, prop, assets, deltas, deltaPath, include, updatingAssets = [], manifest = new Manifest(
						{
							url : Microloader.manifest.url,
							content : result.content,
							assetCache : false
						});

				Microloader.remainingUpdatingAssets = 0;
				Microloader.updatedAssets = [];
				Microloader.removedAssets = [];
				Microloader.updatedManifest = null;
				Microloader.updatedAssetsReady = false;

				// If the updated manifest has turned off caching we need to
				// clear out all local storage
				// and trigger a appupdate as all content is now uncached
				if (!manifest.shouldCache()) {
					// <debug>
					_debug("New Manifest has caching disabled, clearing out any private storage");
					// </debug>

					Microloader.updatedManifest = manifest;
					LocalStorage.clearAllPrivate(manifest);
					Microloader.onAllUpdatedAssetsReady();
					return;
				}

				// Manifest itself has changed
				if (!Microloader.manifest.is(manifest)) {
					Microloader.updatedManifest = manifest;

					currentAssets = Microloader.manifest.getAssets();
					newAssets = manifest.getAssets();

					// Look through new assets for assets that do not exist or
					// assets that have different versions
					for (prop in newAssets) {
						newAsset = newAssets[prop];
						currentAsset = Microloader.manifest
								.getAsset(newAsset.assetConfig.path);
						include = Microloader.filterAsset(newAsset);

						if (include
								&& (!currentAsset || (newAsset.shouldCache() && (!currentAsset
										.is(newAsset))))) {
							// <debug>
							_debug("New/Updated Version of Asset: "
									+ newAsset.assetConfig.path
									+ " was found in new manifest");
							// </debug>
							updatingAssets.push({
										_new : newAsset,
										_current : currentAsset
									});
						}
					}

					// Look through current assets for stale/old assets that
					// have been removed
					for (prop in currentAssets) {
						currentAsset = currentAssets[prop];
						newAsset = manifest
								.getAsset(currentAsset.assetConfig.path);

						// New version of this asset has been filtered out
						include = !Microloader.filterAsset(newAsset);

						if (!include
								|| !newAsset
								|| (currentAsset.shouldCache() && !newAsset
										.shouldCache())) {
							// <debug>
							_debug("Asset: "
									+ currentAsset.assetConfig.path
									+ " was not found in new manifest, has been filtered out or has been switched to not cache. Marked for removal");
							// </debug>
							Microloader.removedAssets.push(currentAsset);
						}
					}

					// Loop through all assets that need updating
					if (updatingAssets.length > 0) {
						Microloader.remainingUpdatingAssets = updatingAssets.length;
						while (updatingAssets.length > 0) {
							assets = updatingAssets.pop();
							newAsset = assets._new;
							currentAsset = assets._current;

							// Full Updates will simply download the file and
							// replace its current content
							if (newAsset.assetConfig.update === "full"
									|| !currentAsset) {

								// <debug>
								if (newAsset.assetConfig.update === "delta") {
									_debug("Delta updated asset found without current asset available: "
											+ newAsset.assetConfig.path
											+ " fetching full file");
								} else {
									_debug("Full update found for: "
											+ newAsset.assetConfig.path
											+ " fetching");
								}
								// </debug>

								// Load the asset and cache its its content into
								// Boot to be evaluated in sequence
								Boot.fetch(newAsset.assetConfig.path,
										(function(asset) {
											return function(result) {
												Microloader
														.onFullAssetUpdateLoaded(
																asset, result)
											};
										}(newAsset)));

								// Delta updates will be given a delta patch
							} else if (newAsset.assetConfig.update === "delta") {
								deltas = manifest.deltas;
								deltaPath = deltas + "/"
										+ newAsset.assetConfig.path + "/"
										+ currentAsset.assetConfig.hash
										+ ".json";
								// Fetch the Delta Patch and update the contents
								// of the asset
								// <debug>
								_debug("Delta update found for: "
										+ newAsset.assetConfig.path
										+ " fetching");
								// </debug>
								Boot.fetch(deltaPath,
										(function(asset, oldAsset) {
											return function(result) {
												Microloader
														.onDeltaAssetUpdateLoaded(
																asset,
																oldAsset,
																result)
											};
										}(newAsset, currentAsset)));
							}
						}
					} else {
						// <debug>
						_debug("No Assets needed updating");
						// </debug>
						Microloader.onAllUpdatedAssetsReady();
					}
				} else {
					// <debug>
					_debug("Manifest files have matching hash's");
					// </debug>
					Microloader.onAllUpdatedAssetsReady();
				}
			} else {
				_warn("Error loading manifest file to check for updates");
				Microloader.onAllUpdatedAssetsReady();
			}
		},

		onFullAssetUpdateLoaded : function(asset, result) {
			var checksum;
			result = Microloader.parseResult(result);
			Microloader.remainingUpdatingAssets--;

			if (!result.error) {
				checksum = Microloader.checksum(result.content,
						asset.assetConfig.hash);
				// <debug>
				_debug("Checksum for Full asset: " + asset.assetConfig.path
						+ " is " + checksum);
				// </debug>
				if (!checksum) {
					// <debug>
					_debug("Full Update Asset: "
							+ asset.assetConfig.path
							+ " has failed checksum. This asset will be uncached for future loading");
					// </debug>

					// uncache this asset as there is a new version somewhere
					// that has not been loaded.
					asset.uncache();
				} else {
					asset.updateContent(result.content);
					Microloader.updatedAssets.push(asset);
				}
			} else {
				// <debug>
				_debug("Error loading file at" + asset.assetConfig.path
						+ ". This asset will be uncached for future loading");
				// </debug>

				// uncache this asset as there is a new version somewhere that
				// has not been loaded.
				asset.uncache();
			}

			if (Microloader.remainingUpdatingAssets === 0) {
				Microloader.onAllUpdatedAssetsReady();
			}
		},

		onDeltaAssetUpdateLoaded : function(asset, oldAsset, result) {
			var json, checksum, content;
			result = Microloader.parseResult(result);
			Microloader.remainingUpdatingAssets--;

			if (!result.error) {
				// <debug>
				_debug("Delta patch loaded successfully, patching content");
				// </debug>
				try {
					json = JSON.parse(result.content);
					content = Microloader.patch(oldAsset.content, json);
					checksum = Microloader.checksum(content,
							asset.assetConfig.hash);
					// <debug>
					_debug("Checksum for Delta Patched asset: "
							+ asset.assetConfig.path + " is " + checksum);
					// </debug>
					if (!checksum) {
						// <debug>
						_debug("Delta Update Asset: "
								+ asset.assetConfig.path
								+ " has failed checksum. This asset will be uncached for future loading");
						// </debug>

						// uncache this asset as there is a new version
						// somewhere that has not been loaded.
						asset.uncache();
					} else {
						asset.updateContent(content);
						Microloader.updatedAssets.push(asset);
					}
				} catch (e) {
					_warn("Error parsing delta patch for "
							+ asset.assetConfig.path
							+ " with hash "
							+ oldAsset.assetConfig.hash
							+ " . This asset will be uncached for future loading");
					// uncache this asset as there is a new version somewhere
					// that has not been loaded.
					asset.uncache();
				}
			} else {
				_warn("Error loading delta patch for " + asset.assetConfig.path
						+ " with hash " + oldAsset.assetConfig.hash
						+ " . This asset will be uncached for future loading");

				// uncache this asset as there is a new version somewhere that
				// has not been loaded.
				asset.uncache();
			}
			if (Microloader.remainingUpdatingAssets === 0) {
				Microloader.onAllUpdatedAssetsReady();
			}
		},

		// TODO: Make this all transaction based to allow for reverting if quota
		// is exceeded
		onAllUpdatedAssetsReady : function() {
			var asset;
			Microloader.updatedAssetsReady = true;

			if (Microloader.updatedManifest) {
				while (Microloader.removedAssets.length > 0) {
					asset = Microloader.removedAssets.pop();
					// <debug>
					_debug("Asset: " + asset.assetConfig.path
							+ " was removed, un-caching");
					// </debug>
					asset.uncache();
				}

				if (Microloader.updatedManifest) {
					// <debug>
					_debug("Manifest was updated, re-caching");
					// </debug>
					Microloader.updatedManifest.cache();
				}

				while (Microloader.updatedAssets.length > 0) {
					asset = Microloader.updatedAssets.pop();
					// <debug>
					_debug("Asset: " + asset.assetConfig.path
							+ " was updated, re-caching");
					// </debug>
					asset.cache();
				}

			}

			Microloader.notifyUpdateReady();
		},

		notifyUpdateReady : function() {
			if (Microloader.appCacheState !== 'checking'
					&& Microloader.updatedAssetsReady) {
				if (Microloader.appCacheState === 'updated'
						|| Microloader.updatedManifest) {
					// <debug>
					_debug("There was an update here you will want to reload the app, trigger an event");
					// </debug>
					Microloader.appUpdate = {
						updated : true,
						app : Microloader.appCacheState === 'updated',
						manifest : Microloader.updatedManifest
								&& Microloader.updatedManifest.exportContent()
					};

					Microloader.fireAppUpdate();
				}
				// <debug>
				else {
					_debug("AppCache and LocalStorage Cache are current, no updating needed");
					Microloader.appUpdate = {};
				}
				// </debug>
			}
		},

		fireAppUpdate : function() {
			if (Ext.GlobalEvents) {
				// We defer dispatching this event slightly in order to let the
				// application finish loading
				// as we are still very early in the lifecycle
				Ext.defer(function() {
							Ext.GlobalEvents.fireEvent('appupdate',
									Microloader.appUpdate);
						}, 1000);
			}
		},

		checksum : function(content, hash) {
			if (!content || !hash) {
				return false;
			}

			var passed = true, hashLn = hash.length, checksumType = content
					.substring(0, 1);

			if (checksumType == '/') {
				if (content.substring(2, hashLn + 2) !== hash) {
					passed = false;
				}
			} else if (checksumType == 'f') {
				if (content.substring(10, hashLn + 10) !== hash) {
					passed = false;
				}
			} else if (checksumType == '.') {
				if (content.substring(1, hashLn + 1) !== hash) {
					passed = false;
				}
			}
			return passed;
		},
		parseResult : function(result) {
			var rst = {};
			if ((result.exception || result.status === 0) && !Boot.env.phantom) {
				rst.error = true;
			} else if ((result.status >= 200 && result.status < 300)
					|| result.status === 304 || Boot.env.phantom
					|| (result.status === 0 && result.content.length > 0)) {
				rst.content = result.content;
			} else {
				rst.error = true;
			}
			return rst;
		}
	};

	return Microloader;
}());

/**
 * @type {String/Object}
 */
Ext.manifest = Ext.manifest || "bootstrap";

Ext.Microloader.run();